#include "game.hpp"

quint8 Game::s_id = 0;

Game::Game(Lobby * lobby, Client * black, quint8 gobanSize, qreal komi, MainWindow * parent) :
    QObject(parent),
    _lobby(lobby),
    _mainWindow(parent),
    _id(s_id++),
    _gobanSize(gobanSize),
    _komi(komi),
    _isStarted(false),
    _isEstablishingScore(false),
    _isFinished(false),
    _blackToPlay(true),
    _lastTurnSkipped(false),
    _blackValidated(false),
    _whiteValidated(false),
    _black(black),
    _white(0),
    _blackScore(0),
    _whiteScore(komi)
{
    _goban = new Goban(_gobanSize, this);

    connect(black, SIGNAL(deconnexion(QString)), this, SLOT(clientLeft(QString)));
    _lobby->addGame(_id, gobanSize, komi, black->name());
}

quint8 Game::id() const
{
    return _id;
}

bool Game::isIn(Client * client) const
{
    return client != 0 and (client == _black or client == _white or _observers.contains(client));
}

bool Game::isPlayingIn(Client * client) const
{
    return client != 0 and (client == _black or client == _white);
}

bool Game::isObserving(Client * client) const
{
    return _observers.contains(client);
}

void Game::reactToPlay(Client * client)
{
    ClientType ct = clientState(client);

    if (ct == BLACK or ct == WHITE)
    {
        _blackValidated = false;
        _whiteValidated = false;

        _black->send(Mid::TOPLAYAGAIN, _id);
        _white->send(Mid::TOPLAYAGAIN, _id);

        for (int i = 0; i < _observers.size(); ++i)
            _observers[i]->send(Mid::TOPLAYAGAIN, _id);
    }
}

void Game::reactValidateGroups(Client * client)
{
    ClientType ct = clientState(client);

    if (ct == BLACK and !_blackValidated)
    {
        _blackValidated = true;

        _black->send(Mid::GROUPSVALIDATED, _id, (quint8) 0);
        _white->send(Mid::GROUPSVALIDATED, _id, (quint8) 0);

        for (int i = 0; i < _observers.size(); ++i)
            _observers[i]->send(Mid::GROUPSVALIDATED, _id, (quint8) 0);
    }
    else if (ct == WHITE and !_whiteValidated)
    {
        _whiteValidated = true;

        _black->send(Mid::GROUPSVALIDATED, _id,(quint8)  1);
        _white->send(Mid::GROUPSVALIDATED, _id, (quint8) 1);

        for (int i = 0; i < _observers.size(); ++i)
            _observers[i]->send(Mid::GROUPSVALIDATED, _id, (quint8) 1);
    }
    else
        client->send(Mid::ILLEGALMESSAGE, "You cannot do that!");

    if (_blackValidated and _whiteValidated)
    {
        _blackScore = 0;
        _whiteScore = _komi;

        _goban->score(_blackScore, _whiteScore);

        _black->send(Mid::GAMEOVER, _id, _blackScore, _whiteScore);
        _black->send(Mid::GAMEDESTROYED, _id);

        _white->send(Mid::GAMEOVER, _id, _blackScore, _whiteScore);
        _white->send(Mid::GAMEDESTROYED, _id);

        for (int i = 0; i < _observers.size(); ++i)
        {
            _observers[i]->send(Mid::GAMEOVER, _id, _blackScore, _whiteScore);
            _observers[i]->send(Mid::GAMEDESTROYED, _id);
        }

        _mainWindow->destroyGame(_id);
    }
}

Game::ClientType Game::clientState(Client * client) const
{
    if (client == 0)
        return NONE;
    else if (client == _black)
        return BLACK;
    else if (client == _white)
        return WHITE;
    else if (_observers.contains(client))
        return OBSERVER;
    else
        return NONE;
}

Game::ClientType Game::clientState(const QString & name) const
{
    if (name.isEmpty())
        return NONE;
    if  (name == _black->name())
        return BLACK;
    else if (_white != 0 and name == _white->name())
        return WHITE;

    for (int i = 0; i < _observers.size(); ++i)
        if (_observers[i]->name() == name)
            return OBSERVER;

    return NONE;
}

Client * Game::whoIsIt(const QString & name) const
{
    if (name.isEmpty())
        return 0;
    if  (name == _black->name())
        return _black;
    else if (_white != 0 and name == _white->name())
        return _white;

    for (int i = 0; i < _observers.size(); ++i)
        if (_observers[i]->name() == name)
            return _observers[i];

    return 0;
}

void Game::addPlayer(Client * client)
{
    if (!_isStarted and
        client != 0 and
        client != _black and
        !_observers.contains(client))
    {
        _white = client;

        _lobby->setWhite(_id, _white->name());

        connect(client, SIGNAL(deconnexion(QString)), this, SLOT(clientLeft(QString)));

        _isStarted = true;

        _lobby->reseau()->sendAll(Mid::GAMESTARTS, _id, _white->name());

        QStringList observerList;

        for (int i = 0; i < _observers.size(); ++i)
            observerList.append(_observers[i]->name());

        _white->send(Mid::OBSERVERLIST, _id, observerList);
    }
}

void Game::addObserver(Client * client)
{
    if (client != 0 and
        !_observers.contains(client) and
        client != _black and
        client != _white)
    {
        client->send(Mid::GAMESTATE, *this);

        _black->send(Mid::HESEES, _id, client->name());

        if (_white != 0)
            _white->send(Mid::HESEES, _id, client->name());

        _observers.append(client);

        for (int i = 0; i < _observers.size(); ++i)
            _observers[i]->send(Mid::HESEES, _id, client->name());

        connect(client, SIGNAL(deconnexion(QString)), this, SLOT(clientLeft(QString)));
    }
}

void Game::clientLeft(const QString & name)
{
    ClientType type = clientState(name);

    if (type != NONE)
    {
        Client * client = whoIsIt(name);

        if (client == 0)
            return;

        if (type == BLACK)
        {
            if (_isStarted)
            {
                if (_white != 0)
                    _white->send(Mid::GAMELEFT, _id, (quint8) 0);
                for (int i = 0; i < _observers.size(); ++i)
                    _observers[i]->send(Mid::GAMELEFT, _id, (quint8) 0);
            }

            _isFinished = true;

            _mainWindow->destroyGame(_id);
            _black = 0;
        }
        else if (type == WHITE)
        {
            _black->send(Mid::GAMELEFT, _id, (quint8) 1);
            for (int i = 0; i < _observers.size(); ++i)
                _observers[i]->send(Mid::GAMELEFT, _id, (quint8) 1);

            _isFinished = true;

            _mainWindow->destroyGame(_id);
            _white = 0;
        }
        else if (type == OBSERVER)
        {
            for (int i= 0; i < _observers.size(); ++i)
            {
                if (_observers[i]->name() == name)
                    _observers.removeAll(_observers[i]);
                else
                    _observers[i]->send(Mid::HEDOESNTSEEANYMORE, _id, name);
            }

            _black->send(Mid::HEDOESNTSEEANYMORE, _id, name);

            if (_white != 0)
                _white->send(Mid::HEDOESNTSEEANYMORE, _id, name);
        }

        disconnect(client, SIGNAL(deconnexion(QString)), this, SLOT(clientLeft(QString)));
    }
}

void Game::reactDeadGroup(quint8 x, quint8 y)
{
    _goban->declareDead(x, y);

    _blackValidated = false;
    _whiteValidated = false;

    _black->send(Mid::GROUPKILLED, _id, x, y);
    _white->send(Mid::GROUPKILLED, _id, x, y);

    for (int i = 0; i < _observers.size(); ++i)
        _observers[i]->send(Mid::GROUPKILLED, _id, x, y);
}

void Game::reactAliveGroup(quint8 x, quint8 y)
{
    _goban->declareAlive(x, y);

    _blackValidated = false;
    _whiteValidated = false;

    _black->send(Mid::GROUPSAVED, _id, x, y);
    _white->send(Mid::GROUPSAVED, _id, x, y);

    for (int i = 0; i < _observers.size(); ++i)
        _observers[i]->send(Mid::GROUPSAVED, _id, x, y);
}

void Game::reactPlayMove(Client * player, quint8 x, quint8 y)
{
    ClientType ct = clientState(player);

    if (_isStarted && !_isEstablishingScore && !_isFinished)
    {
        if (ct == NONE)
            player->send(Mid::ILLEGALMESSAGE, "You have nothing to do with that game");
        else if (ct == OBSERVER)
            player->send(Mid::ILLEGALMESSAGE, "You cannot play in this game");
        else if ((ct == BLACK and _blackToPlay) or (ct == WHITE and !_blackToPlay))
        {
            int ret = _goban->playAMove(ct == BLACK, x, y);

            if (ret == -42)
                player->send(Mid::BADMOVE, _id);
            else
            {
                quint8 idPlayer;
                qreal newScore;

                if (ct == BLACK)
                {
                    idPlayer = 0;
                    _blackScore += ret;
                    newScore = _blackScore;
                }
                else
                {
                    idPlayer = 1;
                    _whiteScore += ret;
                    newScore = _whiteScore;
                }

                _black->send(Mid::MOVEPLAYED, _id, idPlayer, x, y);
                _white->send(Mid::MOVEPLAYED, _id, idPlayer, x, y);

                for (int i = 0; i < _observers.size(); ++i)
                    _observers[i]->send(Mid::MOVEPLAYED, _id, idPlayer, x, y);

                if (ret > 0)
                {
                    _black->send(Mid::NEWSCORE, _id, idPlayer, newScore);
                    _white->send(Mid::NEWSCORE, _id, idPlayer, newScore);

                    for (int i = 0; i < _observers.size(); ++i)
                        _observers[i]->send(Mid::NEWSCORE, _id, idPlayer, newScore);
                }

                _lastTurnSkipped = false;
                _blackToPlay = !_blackToPlay;
            }
        }
        else
            player->send(Mid::ILLEGALMESSAGE, "It is not your turn to play");
    }
}

void Game::skipTurn(Client * client)
{
    ClientType ct = clientState(client);

    if (_isStarted && !_isEstablishingScore && !_isFinished)
    {
        if (ct == NONE)
            client->send(Mid::ILLEGALMESSAGE, "You have nothing to do with that game");
        else if (ct == OBSERVER)
            client->send(Mid::ILLEGALMESSAGE, "You cannot play in this game");
        else if ((ct == BLACK and _blackToPlay) or (ct == WHITE and !_blackToPlay))
        {
            if (_lastTurnSkipped)
            {
                _isEstablishingScore = true;

                QString message;
                if (ct == WHITE)
                    message = _white->name();
                else
                    message = _black->name();

                message += " skipped his turn too! Let's establish a score";

                _black->send(Mid::MOVESFINISHED, _id);
                _white->send(Mid::MOVESFINISHED, _id);

                _black->send(Mid::GAMESAID, _id, message);
                _white->send(Mid::GAMESAID, _id, message);

                for (int i = 0; i < _observers.size(); ++i)
                {
                    _observers[i]->send(Mid::MOVESFINISHED, _id);
                    _observers[i]->send(Mid::GAMESAID, _id, message);
                }
            }
            else
            {
                quint8 playerID = ct == WHITE;
                QString message;
                if (ct == WHITE)
                    message = _white->name();
                else
                    message = _black->name();

                message += " skipped his turn!";

                _black->send(Mid::TURNSKIPPED, _id, playerID);
                _white->send(Mid::TURNSKIPPED, _id, playerID);

                _black->send(Mid::GAMESAID, _id, message);
                _white->send(Mid::GAMESAID, _id, message);

                for (int i = 0; i < _observers.size(); ++i)
                {
                    _observers[i]->send(Mid::TURNSKIPPED, _id, playerID);
                    _observers[i]->send(Mid::GAMESAID, _id, message);
                }

                _blackToPlay = !_blackToPlay;
                _lastTurnSkipped = true;
            }
        }
        else
            client->send(Mid::ILLEGALMESSAGE, "It is not your turn to play");
    }
}

void Game::someoneSaid(const QString & name, const QString & message)
{
    ClientType ct = clientState(name);

    if (ct == NONE)
        return;

    QString toSend = name + " : " + message;

    if (ct == BLACK or ct == WHITE)
    {
        if (_black != 0)
        {
            _black->send(Mid::GAMESAID, _id, toSend);
        }
        if (_white != 0)
        {
            _white->send(Mid::GAMESAID, _id, toSend);
        }
    }

    for (int i = 0; i < _observers.size(); ++i)
    {
        _observers[i]->send(Mid::GAMESAID, _id, toSend);
    }
}

bool Game::destroy(const Client * client)
{
    if (!_isStarted and client == _black)
    {
        kill();
        return true;
    }
    else
        return false;
}

void Game::kill()
{
    disconnect(_black, SIGNAL(deconnexion(QString)), this, SLOT(clientLeft(QString)));

    if (_white != 0)
    {
        disconnect(_white, SIGNAL(deconnexion(QString)), this, SLOT(clientLeft(QString)));
    }

    for (int i = 0; i < _observers.size(); ++i)
    {
        disconnect(_observers[i], SIGNAL(deconnexion(QString)), this, SLOT(clientLeft(QString)));
    }
}

void Game::writeForList(QDataStream & out) const
{
    out << _id
        << _gobanSize
        << _komi
        << _black->name();

    if (_white != 0)
        out << _white->name();
    else
        out << QString();
}

void Game::writeForState(QDataStream & out) const
{
    out << _id;

    QStringList qsl;

    for (int i = 0; i < _observers.size(); ++i)
        qsl.append(_observers[i]->name());

    out << qsl;

    out << _goban->map();
}
